/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * Symmetric Cryptographic Device Base Class
 *      Provides a base class definition for cryptographic implementations that
 * use symmetric algorithms and Key and IV value pairs.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Security.Cryptography;
using Adaptive.Foundation.IO;

namespace Adaptive.Foundation.Security
{
    /// <summary>
    /// Provides a base class definition for cryptographic implementations that
    /// use symmetric algorithms and Key and IV value pairs.
    /// </summary>
    public abstract class SymmetricCryptographicDeviceBase : CryptographicDeviceBase
    {
        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Key value.
        /// </summary>
        private byte[] _key;
        /// <summary>
        /// IV value.
        /// </summary>
        private byte[] _iv;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public SymmetricCryptographicDeviceBase()
            : base()
        {
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                Clear();
            }
            _key = null;
            _iv = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the value of the encryption/descryption key.
        /// </summary>
        /// <value>
        /// A symmetric algorithm encryption or decryption key value.
        /// </value>
        public byte[] Key
        {
            get { return _key; }
            set
            {
                if (ValidateKey(value))
                    _key = value;
            }
        }
        /// <summary>
        /// Gets or sets the value of the encryption/descryption initialization vector (IV).
        /// </summary>
        /// <value>
        /// A symmetric algorithm encryption or decryption IV value.
        /// </value>
        public byte[] IV
        {
            get { return _iv; }
            set
            {
                if (ValidateIV(value))
                    _iv = value;
            }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Event Methods
         *------------------------------------------------------------------------------*/
        #region Protected Methods / Event Methods
        /// <summary>
        /// Validates the specified content as a symmetric encryption key.
        /// </summary>
        /// <param name="keyData">
        /// The data being assigned as the encryption or decryption key.
        /// </param>
        /// <remarks>
        /// Each algorithm's implementation may use keys of varying sizes; thus the 
        /// child implementation must validate the key value accordingly.
        /// </remarks>
        /// <returns>
        /// <b>true</b> if the data is valid; otherwise, <b>false</b>.
        /// </returns>
        protected abstract bool ValidateKey(byte[] keyData);
        /// <summary>
        /// Validates the specified content as a symmetric encryption initialization vector (IV).
        /// </summary>
        /// <param name="keyData">
        /// The data being assigned as the encryption or decryption key.
        /// </param>
        /// <remarks>
        /// Each algorithm's implementation may use IVs of varying sizes; thus the 
        /// child implementation must validate the IVs value accordingly.
        /// </remarks>
        /// <returns>
        /// <b>true</b> if the data is valid; otherwise, <b>false</b>.
        /// </returns>
        protected abstract bool ValidateIV(byte[] keyData);
        /// <summary>
        /// Performs a symmetricd cryptographic operation (encryption or decryption) for the implemented
        /// algorithm object.
        /// </summary>
        /// <param name="operation">
        /// A <see cref="CryptographicOperation"/> instance used to specify the operation to be performed.
        /// </param>
        /// <param name="algorithm">
        /// A <see cref="SymmetricAlgorithm"/> implementation used to perform the encryption or decryption.
        /// </param>
        /// <returns></returns>
        protected virtual bool PerformSymmetricOperation(CryptographicOperation operation, SymmetricAlgorithm algorithm)
        {
            ICryptoTransform transform = null;                  //Cryptographic transformation instance.
            CryptoStream cryptStream = null;                    //Cryptographic transformation stream.
            VirtualMemoryStream outputStream = null;            //Output data container.
            BinaryReader reader = null;                         //Data reader.
            byte[] originalData = null;                         //Data content.
            bool returnValue = false;                           //Return value.

            if (operation.SourceData == null)
            {
                operation.Result.LastException = new ArgumentNullException();
                operation.Result.ResultType = CryptographicResultType.SourceDataException;
                operation.Result.Success = false;
            }
            else
            {
                //Rewind input stream.
                operation.SourceData.Seek(0, SeekOrigin.Begin);

                //Try to create the initial stream and associated instances.
                if (algorithm != null)
                {
                    if (operation.OperationType == CryptographicOperationType.Decrypt)
                        transform = algorithm.CreateDecryptor();
                    else
                        transform = algorithm.CreateEncryptor();

                    //Create the stream(s).
                    outputStream = new VirtualMemoryStream();
                    cryptStream = SafeCreateStream(operation, outputStream, transform);

                    if (cryptStream != null)
                    {
                        reader = new BinaryReader(operation.SourceData);
                        //Perform encryption.
                        do
                        {
                            try
                            {
                                //Read data in 512K blocks.
                                originalData = reader.ReadBytes(524288);
                            }
                            catch (Exception ex)
                            {
                                operation.Result.LastException = ex;
                                operation.Result.ResultData = null;
                                operation.Result.ResultType = CryptographicResultType.UnknownException;
                                operation.Result.Success = false;
                                originalData = null;
                            }

                            if ((originalData != null) && (originalData.Length > 0))
                            {
                                try
                                {
                                    //Encrypt.
                                    cryptStream.Write(originalData, 0, originalData.Length);
                                }
                                catch (CryptographicException cryptoEx)
                                {
                                    operation.Result.LastException = cryptoEx;
                                    operation.Result.ResultData = null;
                                    operation.Result.ResultType = CryptographicResultType.UnknownException;
                                    operation.Result.Success = false;
                                    originalData = null;
                                }
                            }
                        } while ((originalData != null) && (originalData.Length > 0));

                        if (operation.SourceData.Position >= operation.SourceData.Length)
                        {
                            //Flush last data.
                            cryptStream.FlushFinalBlock();
                            
                            outputStream.Seek(0, SeekOrigin.Begin);
                            operation.Result.ResultData = outputStream.Clone();
                            operation.Result.ResultType = CryptographicResultType.Success;
                            operation.Result.Success = true;
                            returnValue = true;
                        }

                        //Clear.
                        reader = null;
                        originalData = null;
                        cryptStream.Close();
                        cryptStream.Dispose();
                    }

                    //Clear.
                    outputStream.Close();
                    outputStream.Dispose();
                    transform.Dispose();
                    algorithm.Clear();
                }

                //Clear and return.
                cryptStream = null;
                outputStream = null;
                transform = null;
            }
            return returnValue;
        }
        /// <summary>
        /// Performs a symmetricd cryptographic operation (encryption or decryption) for the implemented
        /// algorithm object.
        /// </summary>
        /// <param name="operation">
        /// A <see cref="CryptographicOperation"/> instance used to specify the operation to be performed.
        /// </param>
        /// <param name="algorithm">
        /// A <see cref="SymmetricAlgorithm"/> implementation used to perform the encryption or decryption.
        /// </param>
        /// <param name="dataBlocks">
        /// A <see cref="DataBlockCollection"/> containing the data blocks to be encrypted or decrypted.
        /// </param>
        /// <param name="keySets">
        /// The key set container to use.
        /// </param>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        protected virtual bool PerformSymmetricOperation(CryptographicOperation operation, SymmetricAlgorithm algorithm, 
            DataBlockCollection dataBlocks, DerivedKeySetCollection keySets)
        {
            BinaryReader reader = null;                         //Decryption data reader.
            VirtualMemoryStream outputStream = null;            //Main data output container.
            ICryptoTransform transform = null;                  //Cryptographic transformation instance.
            BinaryWriter writer = null;                         //Output data writer.
            bool returnValue = false;                           //Return value.
            int blockIndex = 0;                                 //Index of data block to process.
            int blockLength = 0;                                //Iteration limit.
            int keyIndex = 0;                                   //Key set index.
            DerivedKeySet currentSet = null;                    //Current key set pointer.

            if ((dataBlocks == null) || (dataBlocks.Count == 0))
            {
                operation.Result.LastException = new ArgumentNullException();
                operation.Result.ResultType = CryptographicResultType.SourceDataException;
                operation.Result.Success = false;
            }
            else
            {
                //Create the output container.
                outputStream = new VirtualMemoryStream();
                writer = new BinaryWriter(outputStream);
                if (operation.OperationType == CryptographicOperationType.Decrypt)
                {
                    operation.SourceData.Seek(0, SeekOrigin.Begin);
                    reader = new BinaryReader(operation.SourceData);
                }

                //Iterate through each data block and use a different key each time.
                blockLength = dataBlocks.Count;
                blockIndex = 0;
                returnValue = true;
                do
                {
                    //Get the key set.
                    currentSet = keySets[keyIndex];
                    keyIndex++;
                    if (keyIndex == keySets.Count)
                        keyIndex = 0;

                    //Set the key values.
                    algorithm.Key = currentSet.Key;
                    algorithm.IV = currentSet.IV;

                    //Create the encryptor or decryptor and perform operation.
                    if (operation.OperationType == CryptographicOperationType.Decrypt)
                    {
                        transform = algorithm.CreateDecryptor();
                        int length = reader.ReadInt32();
                        returnValue = DecryptDataBlock(reader.ReadBytes(length), operation, writer, transform);
                    }
                    else
                    {
                        transform = algorithm.CreateEncryptor();
                        returnValue = EncryptDataBlock(dataBlocks[blockIndex], operation, writer, transform);
                    }

                    transform.Dispose();
                    blockIndex++;

                } while ((returnValue) && (blockIndex < dataBlocks.Count));

                if (returnValue)
                {
                    writer.Flush();
                    operation.Result.ResultData = outputStream.Clone();
                    operation.Result.Success = true;
                    operation.Result.ResultType = CryptographicResultType.Success;
                }
                writer.Close();
                outputStream.Close();
                outputStream.Dispose();
            }
            return returnValue;
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Clears the contents of the object instance without disposing of the instance.
        /// </summary>
        public override void Clear()
        {
            base.Clear();
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Creates and returns a cryptographic stream instance.
        /// </summary>
        /// <param name="operation">
        /// The <see cref="CryptographicOperation"/> to be performed, where the result
        /// of the operation is also stored.
        /// </param>
        /// <param name="outputStream">The output stream associated with the cryptographic stream.</param>
        /// <param name="transform">
        /// The cryptographic transformation instance to be applied.
        /// </param>
        /// <remarks>
        /// Any exceptions caught here are stored in the <i>operation</i> parameter.
        /// </remarks>
        /// <returns>
        /// A new <see cref="CryptoStream"/> instance, if successful; otherwise, returns <b>null</b>.
        /// </returns>
        private CryptoStream SafeCreateStream(CryptographicOperation operation, Stream outputStream, ICryptoTransform transform)
        {
            CryptoStream returnValue = null;                    //Return value.

            //Try to create the initial stream and associated instances.
            try
            {
                //Set up cryptography.
                returnValue = new CryptoStream(outputStream, transform, CryptoStreamMode.Write);
            }
            catch (CryptographicException cryptoEx)
            {
                operation.Result.Success = false;
                operation.Result.ResultType = CryptographicResultType.CryptographicException;
                operation.Result.LastException = cryptoEx;
                returnValue = null;
            }
            catch (Exception ex)
            {
                operation.Result.Success = false;
                operation.Result.ResultType = CryptographicResultType.UnknownException;
                operation.Result.LastException = ex;
                returnValue = null;
            }

            return returnValue;
        }
        /// <summary>
        /// Performs encryption of a single data block using the current key set.
        /// </summary>
        /// <param name="block">
        /// A <see cref="DataBlock"/> instance containing the data to be encrypted.
        /// </param>
        /// <param name="operation">
        /// The current <see cref="CryptographicOperation"/> being performed.
        /// </param>
        /// <param name="writer">
        /// A <see cref="BinaryWriter"/> instance used to write data to the main output stream.
        /// </param>
        /// <param name="transform">
        /// The cryptographic transformation to use.
        /// </param>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        private bool EncryptDataBlock(DataBlock block, CryptographicOperation operation, BinaryWriter writer, ICryptoTransform transform)
        {
            MemoryStream outputStream = null;                   //Local data output container.
            CryptoStream cryptStream = null;                    //Cryptographic stream.
            byte[] originalData = null;                         //Original data content.
            bool returnValue = false;                           //Return value.

            //Create streams.
            outputStream = new MemoryStream();
            cryptStream = SafeCreateStream(operation, outputStream, transform);

            if (cryptStream != null)
            {
                //Perform encryption.
                originalData = block.Data;
                if ((originalData != null) && (originalData.Length > 0))
                {
                    try
                    {
                        //Encrypt.
                        cryptStream.Write(originalData, 0, originalData.Length);
                    }
                    catch (CryptographicException cryptoEx)
                    {
                        operation.Result.LastException = cryptoEx;
                        operation.Result.ResultData = null;
                        operation.Result.ResultType = CryptographicResultType.UnknownException;
                        operation.Result.Success = false;
                        originalData = null;
                        returnValue = false;
                    }
                }

                if (originalData != null)
                {
                    //Flush last data.
                    cryptStream.FlushFinalBlock();
                    originalData = outputStream.ToArray();

                    //Store the length and the data so it can be read properly later.
                    writer.Write(originalData.Length);
                    writer.Write(originalData);

                    writer.Flush();
                    returnValue = true;
                }
                else
                    returnValue = false;

                cryptStream.Close();
                cryptStream = null;
            }
            else
            {
                operation.Result.LastException = new ArgumentNullException();
                operation.Result.ResultData = null;
                operation.Result.ResultType = CryptographicResultType.UnknownException;
                operation.Result.Success = false;
            }

            outputStream.Close();
            outputStream.Dispose();

            return returnValue;
        }
        /// <summary>
        /// Performs decryption of a single data block using the current key set.
        /// </summary>
        /// <param name="contentToDecrypt">
        /// A <see cref="DataBlock"/> instance containing the data to be decrypted.
        /// </param>
        /// <param name="operation">
        /// The current <see cref="CryptographicOperation"/> being performed.
        /// </param>
        /// <param name="writer">
        /// A <see cref="BinaryWriter"/> instance used to write data to the main output stream.
        /// </param>
        /// <param name="transform">
        /// The cryptographic transformation to use.
        /// </param>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        private bool DecryptDataBlock(byte[] contentToDecrypt, CryptographicOperation operation, BinaryWriter writer, ICryptoTransform transform)
        {
            MemoryStream outputStream = null;                   //Local data output container.
            CryptoStream cryptStream = null;                    //Cryptographic stream.
            bool returnValue = false;                           //Return value.

            //Create streams.
            outputStream = new MemoryStream();
            cryptStream = SafeCreateStream(operation, outputStream, transform);

            if (cryptStream != null)
            {
                //Perform encryption.
                if ((contentToDecrypt != null) && (contentToDecrypt.Length > 0))
                {
                    try
                    {
                        //Encrypt.
                        cryptStream.Write(contentToDecrypt, 0, contentToDecrypt.Length);
                    }
                    catch (CryptographicException cryptoEx)
                    {
                        operation.Result.LastException = cryptoEx;
                        operation.Result.ResultData = null;
                        operation.Result.ResultType = CryptographicResultType.UnknownException;
                        operation.Result.Success = false;
                        contentToDecrypt = null;
                        returnValue = false;
                    }
                }

                if (contentToDecrypt != null)
                {
                    //Flush last data.
                    cryptStream.FlushFinalBlock();
                    contentToDecrypt = outputStream.ToArray();

                    //Store the length and the data so it can be read properly later.
                    writer.Write(contentToDecrypt);

                    writer.Flush();
                    returnValue = true;
                }
                else
                    returnValue = false;

                cryptStream.Close();
                cryptStream = null;
            }
            else
            {
                operation.Result.LastException = new ArgumentNullException();
                operation.Result.ResultData = null;
                operation.Result.ResultType = CryptographicResultType.UnknownException;
                operation.Result.Success = false;
            }

            outputStream.Close();
            outputStream.Dispose();

            return returnValue;
        }
        #endregion

    }
}